﻿using ErrorDumper.Configuration;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;

namespace ErrorDumper.Providing {
    public static class ExceptionProviders {

        private static bool _isLoaded;
        private static readonly object _syncRoot = new object();
        private static readonly List<IExceptionProvider> _providers = new List<IExceptionProvider>();

        public static IEnumerable<IExceptionProvider> GetProviders() {
            lock (_syncRoot) {
                if (!_isLoaded)
                    Load();
                return _providers.ToList();
            }
        }

        private static void Load() {
            lock (_syncRoot) {
                if (_isLoaded)
                    return;

                _providers.Clear();

                var section = ProvidingConfigurationSection.GetSection();
                if (section != null)
                    foreach (ProviderConfigurationElement config in section.Providers) {
                        switch (config.Reference.ToLowerInvariant()) {
                            case "":
                                var typeName = config.ProviderType;
                                if (string.IsNullOrEmpty(typeName))
                                    throw new ConfigurationErrorsException(
                                        "Attributes 'ref' or 'type' must be specified.",
                                        config.ElementInformation.Source, config.ElementInformation.LineNumber);
                                var type = Type.GetType(typeName, false);
                                if (type == null)
                                    throw new ConfigurationErrorsException(string.Format(
                                        "Type '{0}' specified in attribute 'type' could not be resolved.", typeName),
                                        config.ElementInformation.Source, config.ElementInformation.LineNumber);
                                _providers.Add((IExceptionProvider)Activator.CreateInstance(type));
                                break;

                            //case "sql":
                            //    if (IsRegisteredProviderCore<SqlExceptionProvider>())
                            //        throw new ConfigurationErrorsException(string.Format(
                            //            "Provider with reference '{0}' is already registered.", config.Reference),
                            //            config.ElementInformation.Source, config.ElementInformation.LineNumber);
                            //    _providers.Add(new SqlExceptionProvider());
                            //    break;

                            case "directory":
                                if (IsRegisteredProviderCore<DirectoryExceptionProvider>())
                                    throw new ConfigurationErrorsException(string.Format(
                                        "Provider with reference '{0}' is already registered.", config.Reference),
                                        config.ElementInformation.Source, config.ElementInformation.LineNumber);
                                _providers.Add(new DirectoryExceptionProvider());
                                break;               

                            default:
                                throw new ConfigurationErrorsException(string.Format(
                                    "Provider reference '{0}' is not supported.", config.Reference),
                                    config.ElementInformation.Source, config.ElementInformation.LineNumber);
                        }
                    }

                _isLoaded = true;
            }
        }

        public static void Reset() {
            lock (_syncRoot)
                _isLoaded = false;
        }

        public static void Clear() {
            lock (_syncRoot) {
                _providers.Clear();
                _isLoaded = true;
            }
        }

        public static bool RegisterProvider<TProvider>()
            where TProvider : class, IExceptionProvider, new() {

            lock (_syncRoot) {
                if (!_isLoaded)
                    Load();
                if (_providers.Any(i => i.GetType() == typeof(TProvider)))
                    return false;
                _providers.Add(new TProvider());
                return true;
            }
        }

        private static bool IsRegisteredProviderCore<TProvider>()
            where TProvider : class, IExceptionProvider, new() {

            return _providers.Any(i => i.GetType() == typeof(TProvider));
        }

        public static bool IsRegisteredProvider<TProvider>()
            where TProvider : class, IExceptionProvider, new() {

            lock (_syncRoot) {
                if (!_isLoaded)
                    Load();
                return IsRegisteredProviderCore<TProvider>();
            }
        }

        public static ICollection<ExceptionHeader> GetHeaders(ExceptionQuery query) {

            return
                (from provider in GetProviders()
                 from header in provider.GetHeaders(query)
                 select header)
                .ToList();
        }
    }
}
